//
//  Array+Extension.swift
//  PMS
//
//  Created by 何海涛 on 15/10/16.
//  Copyright © 2015年 途家. All rights reserved.
//

import Foundation
import UIKit

public protocol IAddable {
    init()
    static func +(lhs : Self, rhs : Self) -> Self
}

extension Int : IAddable {}
extension Double : IAddable {}
extension Float : IAddable {}
extension CGFloat : IAddable {}

extension Array {
    public func cast<T>() -> [T] {
        return self.map({ (item) -> T in
            return item as! T
        })
    }
    
    public func cast<T>(_ converter : @escaping (_ item: Element) -> T) -> [T] {
        return self.map({ (item) -> T in
            return converter(item)
        })
    }
    
    public func sum<T : IAddable>(_ selector : @escaping (_ item : Element) -> T) -> T {
        return self.reduce(T(), { i, item in selector(item) + i })
    }
    
    public func ofType<T>() -> [T] {
        return self.filter({e in e is T}).cast()
    }
    
    public func forEach(_ action : (_ index:Int,_ item:Element)->Void) {
        for (i,e) in self.enumerated() {
            action(i, e)
        }
    }
    
    public func toDictionary<TKey>(_ keySelector : (_ e:Element)->TKey) -> Dictionary<TKey, Element> {
        var result = Dictionary<TKey,Element>()
        for item in self {
            let key = keySelector(item)
            result[key] = item
        }
        return result
    }
    
    public func toDictionary<TKey,TValue>(_ keySelector : (_ e: Element)->TKey, valueSelector :(_ e:Element)->TValue) -> Dictionary<TKey,TValue> {
        var result = Dictionary<TKey,TValue>()
        for item in self {
            result[keySelector(item)]=valueSelector(item)
        }
        return result
    }
    
    public func groupBy<TKey : Hashable>(_ keySelector : (_ e: Element)->TKey) -> Dictionary<TKey,[Element]> {
        return self.groupBy(keySelector, valueSelector: { $0 })
    }
    
    public func groupBy<TKey : Hashable,TValue>(_ keySelector : (_ e:Element)->TKey,valueSelector: (_ e:Element)->TValue) -> Dictionary<TKey,[TValue]> {
        var result = Dictionary<TKey,[TValue]>()
        for item in self {
            let key = keySelector(item)
            var array = result[key] ?? [TValue]()
            array.append(valueSelector(item))
            result[key] = array
        }
        return result
    }
    
//    func groupByToArray<TKey: Hashable>(keySelector: (e: Element) -> TKey) -> [(TKey, [Element])] {
//        var keys: [TKey] = []
//        for e in self {
//            let key = keySelector(e: e)
//            if !keys.contains(key) {
//                keys.append(key)
//            }
//        }
//        let dict = self.groupBy(keySelector, valueSelector: { $0 })
//        var ret: [(TKey, [Element])] = []
//        for key in keys {
//            ret.append((key, dict[key] ?? []))
//        }
//        return ret
//    }
    @discardableResult
    public func first(_ predicate : (_ e:Element)->Bool) -> Element? {
        for item in self {
            if predicate(item) {
                return item
            }
        }
        return nil
    }
    
    public func distinct<TKey: Equatable>(_ keySelector:(_ e : Element)->TKey) -> [TKey] {
        var result = [TKey]()
        for item in self {
            let key = keySelector(item)
            if !result.contains(key) {
                result.append(key)
            }
        }
        return result
    }
    
    public func distinctBy<TKey: Equatable, TValue>(_ keySelector: (_ e: Element)->TKey, valueSelector:(_ e: Element)->TValue) -> [TValue] {
        var result = [TValue]()
        var passedKeys = [TKey]()
        for item in self {
            let key = keySelector(item)
            if !passedKeys.contains(key) {
                passedKeys.append(key)
                result.append(valueSelector(item))
            }
        }
        return result
    }
    
    public func distinctBy<TKey: Equatable>(_ keySelector: (_ e: Element)->TKey) -> [Element] {
        var result = [Element]()
        var passedKeys = [TKey]()
        for item in self {
            let key = keySelector(item)
            if !passedKeys.contains(key) {
                passedKeys.append(key)
                result.append(item)
            }
        }
        return result
    }
}

extension Collection where Iterator.Element : IAddable {
    public func sum() -> Iterator.Element {
        return self.reduce(Iterator.Element(), { $0 + $1 })
    }
}

extension Collection {
    
    public func all(_ predicate: (_ e: Iterator.Element)-> Bool) -> Bool {
        for item in self {
            if !predicate(item) {
                return false
            }
        }
        return true
    }
    
    public func any(_ predicate: (_ e: Iterator.Element)-> Bool) -> Bool {
        for item in self {
            if predicate(item) {
                return true
            }
        }
        return false
    }
    
    public func minAndMax<T: Comparable>(_ selector: (Iterator.Element)-> T) -> (min: T?, max: T?) {
        guard self.count > 0 else { return (nil, nil) }
        let sorted = self.sorted(by: { selector($0)<selector($1) })
        return (selector(sorted.first!), selector(sorted.last!))
    }
    
    public func min<T: Comparable>(_ selector: (Iterator.Element)-> T) -> T? {
        return minAndMax(selector).min
    }
    
    public func max<T: Comparable>(_ selector: (Iterator.Element)-> T) -> T? {
        return minAndMax(selector).max
    }
    
    public func comparableRangeForeach<T: Comparable>(_ selector: (Iterator.Element)->T, firstMin min: T, firstMax max: T, each: (Iterator.Element)->Void) {
        for item in self {
            if selector(item) > max {
                break
            }
            if selector(item) >= min {
                each(item)
            }
        }
    }
}

extension Dictionary {
    public func first(_ predicate: (_ e: Element)->Bool) -> Element? {
        for ele in self {
            if predicate(ele) {
                return ele
            }
        }
        return nil
    }
}

public func +<K:Hashable,V>(lhs: Dictionary<K,V>, rhs: Dictionary<K,V>) -> Dictionary<K,V> {
    var result = lhs
    for (k,v) in rhs {
        result[k] = v
    }
    return result
}
